package com.KOL.present;

import java.awt.*;
import java.util.*;
import java.awt.image.*;
import java.io.*;

import com.util.ImagesLoader;
import com.KOL.model.Enemy;
import com.KOL.model.Equipment;
import com.KOL.model.TiledEntity;
import com.KOL.model.WorldItems;
import com.KOL.model.character.PlayerCharacter;
import com.KOL.model.items.BeltArmor;
import com.KOL.model.items.BootsArmor;
import com.KOL.model.items.ChestArmor;
import com.KOL.model.items.CloakArmor;
import com.KOL.model.items.GenericItem;
import com.KOL.model.items.GreavesArmor;
import com.KOL.model.items.HandsArmor;
import com.KOL.model.items.HeadArmor;
import com.KOL.model.items.Item;
import com.KOL.model.items.NecklaceArmor;
import com.KOL.model.items.ShoulderArmor;
import com.KOL.model.items.TrinketArmor;

public class ChunkDisplay {
  private final static String WORLD_DIR = "../../../data/chunks/";

  public TileType tileType;

  private int numXTiles, numYTiles;
  private int tileWidth, tileHeight;
  private int evenRowX, evenRowY;
  private int oddRowX, oddRowY;

  /* Pixel offset for drawing the top-left corner of the floor
     image (and all of its contents) relative to the top-left
     corner (0,0) of the JPanel. */
  private int xOffset, yOffset;

  private ImagesLoader imsLoader;
  private BufferedImage floorIm;

  private boolean obstacles[][];
  private WorldItems wItems;
  private Section section;

  private PlayerCharacter player;
  private Enemy enemies[];
  private int numEnemiesPerSection;
  private int blocksCounter;
  private int numItems;


  public ChunkDisplay(ImagesLoader imsLd, Section sect) {
   numEnemiesPerSection = 0;
   imsLoader = imsLd;
   section = sect;
   blocksCounter = 0;
   numItems = 0;
   xOffset = -165;
   yOffset = 240;
   loadFloorInfo(section.getSection() + ".dat");

   wItems = new WorldItems(tileWidth, tileHeight,
                             evenRowX, evenRowY, oddRowX, oddRowY);
   initObstacles();
   loadWorldObjects(section.getSection() + ".env");
  }


  // ----------------- load floor info ------------------

  private void loadFloorInfo(String wFNm)
  /* The format of the input lines are:
        image <name>          // the name of the floor GIF
        totalTiles  x  y        // number of tiles in the x- and y- directions
        tileSize w h           // the width and height of a single tile
        evenRow x y           // start of first even row
        oddRow x y            // start of first odd row
     and blank lines and comment lines.
  */
  {
    String worldFNm = WORLD_DIR + wFNm;
    System.err.println("Reading file: " + worldFNm);
    try {
      InputStream in = this.getClass().getResourceAsStream(worldFNm);
      BufferedReader br = new BufferedReader( new InputStreamReader(in));
      String line;
      String[] tokens;
      while((line = br.readLine()) != null) {
        if (line.length() == 0)  // blank line
          continue;
        if (line.startsWith("#"))   // comment
          continue;

        tokens = line.split("\\s+");
        if (tokens[0].equals("image"))
          floorIm = imsLoader.getImage(tokens[1]);  // load image
        else if (tokens[0].equals("totalTiles")) {
          numXTiles = getNumber(tokens[1]);
          numYTiles = getNumber(tokens[2]);
        }
        else if (tokens[0].equals("tileSize")){
          tileWidth = getNumber(tokens[1]);
          tileHeight = getNumber(tokens[2]);
        }
        else if (tokens[0].equals("evenRow")){
          evenRowX = getNumber(tokens[1]);
          evenRowY = getNumber(tokens[2]);
        }
        else if (tokens[0].equals("oddRow")){
          oddRowX = getNumber(tokens[1]);
          oddRowY = getNumber(tokens[2]);
        }
        else
          System.err.println("Do not recognize line: " + line);
      }
      br.close();
    }
    catch (IOException e)
    { System.err.println("Error reading file: " + worldFNm);
      System.exit(1);
    }
  }

  private int getNumber(String token)  {
    int num = 0;
    try {
      num = Integer.parseInt(token);
    }
    catch (NumberFormatException ex){
      System.err.println("Incorrect format for " + token);
    }
    return num;
  }

  private void loadWorldObjects(String woFNm) {
    String objsFNm = WORLD_DIR + woFNm;
    System.err.println("Reading file: " + objsFNm);
    try {
      InputStream in = this.getClass().getResourceAsStream(objsFNm);
      BufferedReader br = new BufferedReader( new InputStreamReader(in));

      String line;
      char ch;
      while((line = br.readLine()) != null) {
        if (line.length() == 0)
          continue;
        if (line.startsWith("#"))
          continue;
        ch = Character.toLowerCase( line.charAt(0) );
        if (ch == 'n')
          getObstacles(line.substring(1), br);
        else if (ch == 'e')
            numEnemiesPerSection = Integer.parseInt(line.substring(1).replaceAll(";", " ").trim());
        else if (ch == 'b')
          getBlocks(line, br);
        else if (ch == 'i')
          getDroppedItem(line, br);
        else
          System.err.println("Do not recognize line: " + line);
      }
      br.close();
    }
    catch (IOException e)
    { System.err.println("Error reading file: " + objsFNm);
      System.exit(1);
    }
  }

  private void getObstacles(String line, BufferedReader br) {
    boolean reachedEnd = getObstaclesLine(line);
    try {
      while (!reachedEnd) {
        line = br.readLine();
        if (line == null) {
          System.err.println("Unexpected end of obstacles info");
          System.exit(1);
        }
        reachedEnd = getObstaclesLine(line);
      }
    }
    catch (IOException e)
    { System.err.println("Error reading obstacles info");
      System.exit(1);
    }
  }

  private boolean getObstaclesLine(String line) {
    StringTokenizer tokens = new StringTokenizer(line);
    String token;
    Point coord;
    while (tokens.hasMoreTokens()) {
      token = tokens.nextToken();
      if (token.equals(";"))   // end of coords
        return true;
      coord = getCoord(token);
      obstacles[coord.x][coord.y] = true;  
    }
    return false;
  }

  private Point getCoord(String token) {
    int x = 0;
    int y = 0;
    String[] results = token.split("-");
    if (results.length != 2)
      System.err.println("Incorrect coordinates in " + token);
    else {
      try {
        x = Integer.parseInt(results[0]);
        y = Integer.parseInt(results[1]);
      }
      catch (NumberFormatException ex){
        System.err.println("Incorrect format for coordinates in " + token);
      }
    }

    if (x >= numXTiles) {
      System.err.println("x coordinate too large in " + token);
      x = numXTiles-1;
    }
    if (y >= numYTiles) {
      System.err.println("x coordinate too large in " + token);
      x = numYTiles-1;
    }

    return new Point(x,y);
  } 

  private void getBlocks(String line, BufferedReader br) {
    boolean reachedEnd = false;
    StringTokenizer tokens = new StringTokenizer(line);
    tokens.nextToken();     // skip 'b'
    String blockName = tokens.nextToken();
    BufferedImage blockIm = imsLoader.getImage(blockName);

    try {
      while (!reachedEnd) {
        line = br.readLine();
        if (line == null) {
          System.err.println("Unexpected end of blocks info");
          System.exit(1);
        }
        reachedEnd = getBlocksLine(line, blockName, blockIm);
      }
    }
    catch (IOException e)
    { System.err.println("Error reading blocks info");
      System.exit(1);
    }
  }

  private boolean getBlocksLine(String line, String blockName, BufferedImage im) {
    StringTokenizer tokens = new StringTokenizer(line);
    String token;
    Point coord;
    while (tokens.hasMoreTokens()) {
      token = tokens.nextToken();
      if (token.equals(";"))
        return true;
      coord = getCoord(token);

      wItems.addObsticalItem( blockName+blocksCounter, TileType.BLOCK, coord.x, coord.y, im);
      obstacles[coord.x][coord.y] = true;
      blocksCounter++;
    }
    return false;
  }

  private void getDroppedItem(String line, BufferedReader br) {
    boolean reachedEnd = false;
    StringTokenizer tokens = new StringTokenizer(line);
    tokens.nextToken(); // skip 'i'
    String droppedItemName = tokens.nextToken();
    BufferedImage droppedItemIm = imsLoader.getImage(droppedItemName);


    Item item = getDroppedItemLine(tokens);
    Point coord = getCoord(tokens.nextToken());
    wItems.addDroppedItem(item,droppedItemName, coord.x, coord.y, droppedItemIm);
    numItems++;
  }

  private Item getDroppedItemLine(StringTokenizer tokens) {
    Item item;
    String token;
    tokens.nextToken(); // skip '-e'
    token = tokens.nextToken();

    if(token.equals("GenericItem"))
        item = parseItem(tokens);
    else
        item = parseEquipment(tokens, token);

    return item;
  }

  private GenericItem parseItem(StringTokenizer tokens) {
      GenericItem item = null;
      String token;
      String name = "-1";
      String description = "-1";
      int weight = -1;

      while (tokens.hasMoreTokens()) {
        token = tokens.nextToken();
        if (token.equals("-name"))
            name = tokens.nextToken();
        else if(token.equals("-desc"))
            description = tokens.nextToken();
        else if(token.equals("-weight"))
            weight = Integer.parseInt(tokens.nextToken());
        else if (token.equals(";")){
            if(name.equals("-1") || description.equals("-1") || weight == -1) {
                System.err.println("Unable able to add generic item, missing parameters for: " + name);
                item = null;
            }else
                item = new GenericItem(name,description,weight);
        }else
            System.err.println("Do not equipment line symbol: " + token);
      }

      return item;
  }

  private Equipment parseEquipment(StringTokenizer tokens, String type) {
      Equipment equipment = null;
      String token;
      String name = "-1";
      int levelRequirement = -1;
      int str = -1;
      int dex = -1;
      int tough = -1;
      int intel = -1;
      int wis = -1;
      int def = -1;

      while (tokens.hasMoreTokens()) {
        token = tokens.nextToken();
        if (token.equals("-name"))
            name = tokens.nextToken();
        else if(token.equals("-lvlReq"))
            levelRequirement = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-str"))
            str = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-dex"))
            dex = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-tough"))
            tough = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-intel"))
            intel = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-wis"))
            wis = Integer.parseInt(tokens.nextToken());
        else if(token.equals("-def"))
            def = Integer.parseInt(tokens.nextToken());
        else if (token.equals(";")){
            if(name.equals("-1") || levelRequirement == -1 || str == -1 ||
               dex == -1 || tough == -1 || intel == -1 || wis == -1 ||
               def == -1) {
                System.err.println("Unable able to add equipment, missing parameters for type: " + type);
                equipment = null;
            }else
                equipment = getEquipmentObject(type, name,levelRequirement,str,dex,tough,intel,wis,def);
        }else
            System.err.println("Do not equipment line symbol: " + token);
      }

      return equipment;
  }

  private Equipment getEquipmentObject(String type, String name, int lvlReq, int str, int dex,int tough, int intel, int wis, int def) {
      Equipment equipment = null;

      if(type.equals("Belt"))
          equipment = new BeltArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Boots"))
          equipment = new BootsArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Chest"))
          equipment = new ChestArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Cloak"))
          equipment = new CloakArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Greaves"))
          equipment = new GreavesArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Hands"))
          equipment = new HandsArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Head"))
          equipment = new HeadArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Necklace"))
          equipment = new NecklaceArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("ShoulderArmor"))
          equipment = new ShoulderArmor(name,lvlReq,str, dex,tough,intel,wis,def);
      else if(type.equals("Trinket"))
          equipment = new TrinketArmor(name,lvlReq,str, dex,tough,intel,wis,def);

      return equipment;
  }

  private void initObstacles() {
    obstacles = new boolean[numXTiles][numYTiles];
    for(int i=0; i < numXTiles; i++)
      for(int j=0; j < numYTiles; j++)
        obstacles[i][j] = false;
  }

  public boolean validTileLoc(int x, int y) {
    if ((x < 0) || (x >= numXTiles) || (y < 0) || (y >= numYTiles))
      return false;
    if (obstacles[x][y])
      return false;
    return true;
  } 

  public void draw(Graphics g)
  /* Draw the world (with all its contents) to the screen.
     The WorldItems object already contains the blocks and pickups.

     The sprites must be added, and then WorldItems' draw() method
     is called. The sprites are then removed, since they will
     probably move before the next draw, and will need to be added
     again in a new position.
  */
  {
    g.drawImage(floorIm, xOffset, yOffset, null);
    wItems.positionSprites(player, enemies);
    wItems.draw(g, xOffset, yOffset);
    wItems.removeEntity();
  }

  public String overItem(Point pt) {
    return wItems.findItemName(pt);
  }

  public void removeItem(String name) {
    Item item = wItems.getItem(name);
    if (wItems.removeItem(name)) {
      numItems--;
      if(item != null)
          player.pickUpItem(item);
    }
    else
      System.err.println("Cannot delete unknown item: " + name);
  }

  public int getNumberItems() {
      return numItems;
  }

  public int getTotalEnemiesForSection() {
      return numEnemiesPerSection;
  }

  public Point nearestItem(Point pt)
  { return wItems.nearestItem(pt); }

  public void addEntities(PlayerCharacter pc, Enemy enemArr[]) {
    player = pc;
    enemies = enemArr;
  }

  public Point getPlayerLoc()
  {  return player.getTileLoc(); }


  public void hitByEnemy(Enemy enemy)
  {
      //TODO Implement battle
      player.attacked();
  }

  public void playerHasMoved(Point newPt, int moveQuad) {
    for(int i=0; i < enemies.length; i++)
        enemies[i].playerHasMoved(newPt);

    updateOffsets(moveQuad);
  }

  private void updateOffsets(int moveQuad) {
    // TODO: Implement 8 dir

    if (moveQuad == TiledEntity.SW) {   // offset to NE
        xOffset += tileWidth/2;
        yOffset -= tileHeight/2;
    }
    else if (moveQuad == TiledEntity.NW) {  // offset to SE
        xOffset += tileWidth/2;
        yOffset += tileHeight/2;
    }
    else if (moveQuad == TiledEntity.NE) {  // offset to SW
        xOffset -= tileWidth/2;
        yOffset += tileHeight/2;
    }
    else if (moveQuad == TiledEntity.SE) {  // offset to NW
        xOffset -= tileWidth/2;
        yOffset -= tileHeight/2;
    }
    else if (moveQuad == TiledEntity.STILL) {
    }
    else
      System.err.println("moveQuad error detected");
  }
}
